/*
 *	OpenSource code or fragments by agile webentwicklung, Marcus Fuhrmeister
 *
 *	Redistribution and use in source and binary forms, with or without
 *	modification, are permitted provided that the following conditions
 *	are met:
 *  
 *		* Redistribution of source code must retain the above OpenSource comment,
 *		this list of conditions and the following disclaimer.
 * 
 *		* Redistribution in binary form must reproduce the above OpenSource comment,
 *		this list of conditions and the following disclaimer in the
 *		documentation and/or other materials provided with the distribution.
 * 
 *	Neither the name of 'avocaod systems, Marcus Fuhrmeister' or the
 *	names of contributors may be used to endorse or promote products derived
 *	from this software without specific prior written permission.
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 2 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 
 */
package de.fuhrmeister.browserchooser.controller;

import java.awt.Component;
import java.awt.Frame;
import java.awt.Toolkit;
import java.awt.datatransfer.DataFlavor;
import java.awt.datatransfer.Transferable;
import java.awt.dnd.DnDConstants;
import java.awt.dnd.DropTargetDropEvent;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;

import javax.swing.UIManager;

import de.fuhrmeister.browserchooser.model.ModelFactory;
import de.fuhrmeister.lm4j.LogMonitor;
import de.fuhrmeister.util.ConfigManager;
import de.fuhrmeister.util.FileChooser;
import de.fuhrmeister.util.FileManager;
import de.fuhrmeister.util.MessageBox;
import de.fuhrmeister.util.OSValidator;
import de.fuhrmeister.util.Tools;

/**
 * Main controller class handling the application.
 * 
 * @date 20.11.2008
 * @author Marcus Fuhrmeister
 */
public class Controller {

	public static final int APP_WINDOW_MAIN = 1;

	public static final int SYSTEM_SCREEN_WIDTH = (int) Toolkit.getDefaultToolkit().getScreenSize().getWidth();

	public static final int SYSTEM_SCREEN_HEIGHT = (int) Toolkit.getDefaultToolkit().getScreenSize().getHeight();

	private static Controller controller;

	private static boolean isStartingApp;

	/**
	 * Singleton pattern.
	 * 
	 * @return this
	 */
	public static synchronized Controller getInstance() {
		if (controller == null) {
			Controller.controller = new Controller();
			return Controller.controller;
		}
		return Controller.controller;
	}

	/**
	 * Inits the application.
	 * Calls to init the tools, the logger, the model and the LAF.
	 * Calls to load the views. 
	 */
	public void load() {
		Controller.isStartingApp = true;
		Tools.init();
		initLogger();
		initModel();
		setLockAndFeel();
		loadViews();
		Controller.isStartingApp = false;
	}

	/**
	 * Inits the log monitor.
	 */
	private void initLogger() {
		final LogMonitor logHandler = LogMonitor.getInstance();
		logHandler.setMonitor(LogMonitor.FILE);
		Tools.logManager.setLevel(Level.ALL);
		Tools.logManager.addHandler(logHandler);
	}

	/**
	 * Calls to load the model if the model file exits.
	 */
	private void initModel() {
		if (ModelFactory.modelFileExists()) {
			try {
				ModelFactory.load();
			} catch (final Exception e) {
				Tools.messageBox.inform(null, Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_INFORMATION), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_MODELLOADING));
				Tools.logManager.finest(Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_MODELLOADING)+ ": "+e.getMessage());
				e.printStackTrace();
			}
		}
	}

	/**
	 * Sets the LAF.
	 */
	private void setLockAndFeel() {
		try {
			UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		} catch (final Exception e) {
			Tools.logManager.finest(e.getMessage());
		}
	}

	/**
	 * Calls to load the views.
	 */
	private void loadViews() {
		DockletController.getInstance().show();
	}

	/**
	 * Calls to save the model, to init the model and to update the GUI.
	 */
	public void update() {
		ModelFactory.save();
		this.initModel();
		this.updateGUI();
	}
	
	/**
	 * Calls to update the language and the GUI.
	 */
	public void updateGUI() {
		Tools.configMng.updateLanguage();
		DockletController.getInstance().updateGUI();
	}

	/**
	 * Calls to save the preferences, to close the views,
	 * to save the model and calls the system to exit the application.
	 */
	public void exit() {
		Tools.configMng.savePreferences();
		DockletController.getInstance().exit();
		ModelFactory.save();
		System.gc();
		System.exit(0);
	}

	/**
	 * Calls to set the new language preference and to update the application.
	 * 
	 * @param text the new language
	 */
	public void updateLanguage(final String text) {
		if (Tools.configMng.languageExists(text)) {
			Tools.configMng.setPreference(ConfigManager.LANGUAGE, text);
			Tools.configMng.updateLanguage();
			DockletPopupMenuController.getInstance().updateLanguage();
		} else {
			Tools.messageBox.error(DockletController.getInstance().getWindow(), Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_ERROR), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_FILENOTEXISTS_LANGUAGE));
		}

	}

	/**
	 * Opens a <code>FileChooser</code> to choose a language file.
	 * Calls to copy the language file into the workspace and calls to update the application.
	 */
	public void loadLanguage() {
		final FileChooser chooser = new FileChooser(false);
		chooser.setLocale(Tools.configMng.getLocale());
		chooser.setFileFilter(Tools.configMng.getConfiguration(ConfigManager.STRINGS_FILEEXT), Tools.configMng.getStringFor(ConfigManager.STRING_FILEEXT_DESC));
		if (chooser.showOpenDialog(DockletController.getInstance().getWindow())) {
			if (copyLanguageFile(chooser.getDirectory(), chooser.getFileName())) {
				updateLanguage(chooser.getFileName().substring(0, chooser.getFileName().indexOf(".")));
			}
		}
	}

	/**
	 * Calls to copy the language file into the workspace.
	 * 
	 * @param dir the directory the file will be copied to
	 * @param file the language file
	 * @return
	 */
	private boolean copyLanguageFile(final String dir, final String file) {
		try {
			Tools.fileManager.copy(dir, file, false, Tools.configMng.getConfiguration(ConfigManager.STRINGS_FOLDER), true);
		} catch (final IOException e) {
			// TODO: check for close() FileOutputException, this is no copy fault
			Tools.messageBox.error(DockletController.getInstance().getWindow(), Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_ERROR), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_COPY_LANGUAGE));
			return false;
		}
		return true;
	}

	/**
	 * Calls to focus on the specified component.
	 * 
	 * @param object the key representing the component
	 */
	public void focus(final int object) {
		switch (object) {
		case Controller.APP_WINDOW_MAIN: {
			DockletController.getInstance().focus();
		}
		}
	}

	/**
	 * Calls to get the specified window.
	 * 
	 * @param object the key representing the component
	 * @return the component
	 */
	public Component getWindow(final int object) {
		switch (object) {
		case Controller.APP_WINDOW_MAIN: {
			return DockletController.getInstance().getWindow();
		}
		default: {
			return DockletController.getInstance().getWindow();
		}
		}
	}
	
	/**
	 * Calls to return the specified frame.
	 * 
	 * @param object the key representing the component
	 * @return the frame
	 */
	public Frame getFrame(int object) {
		switch (object) {
		case Controller.APP_WINDOW_MAIN: {
			return DockletController.getInstance().getWindow();
		}
		default: {
			return DockletController.getInstance().getWindow();
		}
		}
	}

	/**
	 * Returns the OS related executables.
	 * 
	 * @return the executables
	 */
	public static String[] getExecutables() {
		if (OSValidator.isWindows()) {
			return new String[] { "exe", "bat", "jar", "lnk" };
		} else if (OSValidator.isMac()) {
			return new String[] { "app", "dmg", "jar" };
		} else if (OSValidator.isUnix()) {
			return new String[] { "bin", "sh", "jar" };
		} else {
			return new String[] { "jar" };
		}
	}

	/**
	 * Returns the OS related executables description.
	 * 
	 * @return the executables description
	 */
	public static String getExecutablesDesc() {
		if (OSValidator.isWindows()) {
			return "exe, bat, jar, lnk";
		} else if (OSValidator.isMac()) {
			return "app, dmg, jar";
		} else if (OSValidator.isUnix()) {
			return "bin, sh, jar";
		} else {
			return "jar";
		}
	}

	/**
	 * Checks if the given string is an executable.
	 * 
	 * @param exec the executable
	 * @return true if the given string is an executable
	 */
	public static boolean isExecutable(String exec) {
		for (String execSufix : getExecutables()) {
			if (exec.endsWith(execSufix)) return true;
		}
		return false;
	}
	
	/**
	 * Checks if the given string is an OS related system link.
	 * 
	 * @param exec
	 * @return
	 */
	public static boolean isOSLink(String exec) {
		if (OSValidator.isWindows()) {
			return exec.endsWith("lnk");
		} else if (OSValidator.isMac()) {
			return exec.endsWith("ln");
		} else if (OSValidator.isUnix()) {
			return exec.endsWith("ln");
		} else {
			return false;
		}
	}
	
	/**
	 * Indicates if the application is starting.
	 * 
	 * @return true if the application is starting
	 */
	public boolean isStartingApp() {
		return Controller.isStartingApp;
	}

	/**
	 * Calls to extract the dropped files and calls to show the settings dialog.
	 * 
	 * @param dtde the DropTargetDropEvent
	 */
	public void dropBrowser(final DropTargetDropEvent dtde) {
		final Iterator<String> i = this.getDroppedFiles(dtde).iterator();
		while (i.hasNext()) {
			final String filePath = i.next();
			final File file = new File(filePath);
			if (!file.isFile()) {
				Tools.messageBox.alert(DockletController.getInstance().getWindow(), Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_INFORMATION), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_NODRAGABLEFILE), MessageBox.INFORMATION_MESSAGE);
				return;
			}
			SettingsController.getInstance().show(file.getName(), file.getAbsolutePath());
		}
	}

	/**
	 * Calls to extract the dropped files and calls to show the settings dialog.
	 * 
	 * @param dtde the DropTargetDropEvent
	 * @param id the browser id
	 */
	public void dropVersion(final DropTargetDropEvent dtde, final int id) {
		final Iterator<String> i = this.getDroppedFiles(dtde).iterator();
		while (i.hasNext()) {
			final String filePath = i.next();
			final File file = new File(filePath);
			if (!file.isFile()) {
				Tools.messageBox.alert(DockletController.getInstance().getWindow(), Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_INFORMATION), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_NODRAGABLEFILE), MessageBox.INFORMATION_MESSAGE);
				return;
			}
		}
		SettingsController.getInstance().show(id);
	}

	/**
	 * Gets called when the drop was successfull. Extracts the file path and
	 * calls to show the settings dialog.
	 * 
	 * @param files the list of files
	 */
	@SuppressWarnings("unused")
	private static final void dropFiles(final List<String> files) {
		final Iterator<String> i = files.iterator();
		while (i.hasNext()) {
			final String filePath = i.next();
			final File file = new File(filePath);
			if (!file.isFile()) {
				Tools.messageBox.alert(DockletController.getInstance().getWindow(), Tools.configMng.getStringFor(ConfigManager.MESSAGE_TITLE_INFORMATION), Tools.configMng.getStringFor(ConfigManager.MESSAGE_VALUE_ERROR_NODRAGABLEFILE), MessageBox.INFORMATION_MESSAGE);
				return;
			}
			SettingsController.getInstance().show(file.getName(), file.getAbsolutePath());
		}
	}

	/**
	 * Gets the transfer data for the specified data flavour and returns the created list of files.
	 * 
	 * @param dtde the DropTargetDropEvent
	 * @return the list of files
	 */
	private List<String> getDroppedFiles(DropTargetDropEvent dtde) {
		dtde.getLocation();
		boolean success = false;
		if ((dtde.getSourceActions() & DnDConstants.ACTION_COPY) == 0) {
			dtde.rejectDrop();
		} else {
			dtde.acceptDrop(DnDConstants.ACTION_LINK);
			final Transferable trans = dtde.getTransferable();
			final DataFlavor[] currentFlavors = dtde.getCurrentDataFlavors();
			DataFlavor selectedFlavor = null;
			for (int i = 0; i < currentFlavors.length; i++) {
				if (DataFlavor.javaFileListFlavor.equals(currentFlavors[i])) {
					selectedFlavor = currentFlavors[i];
					break;
				}
			}
			final List<String> vFiles = new ArrayList<String>();
			if (selectedFlavor != null) {
				try {
					final Object object = trans.getTransferData(selectedFlavor);
					if (object instanceof List)
					{
						@SuppressWarnings("unchecked")
						final List<File> list = (List<File>) object;
						final Iterator<File> files = list.iterator();
						while (files.hasNext()) {
							File file = files.next();
							try {
								if (Controller.isOSLink(file.getName())) {
									// get linked source file if any - for x86 based
									// architecture only
									final String fileSource = FileManager.getInstance().parseLink(file);
									if (!fileSource.isEmpty())
										file = new File(fileSource);
								}
							} catch (NullPointerException npe) {
								// an exception does no harm to the business logic
							}
							vFiles.add(file.getPath());
							success = true;
						}
					}
				} catch (final ClassCastException e) {

				} catch (final Exception ex) {

				}
			}
			dtde.getDropTargetContext().dropComplete(success);
			if (vFiles.size() > 0) {
				return vFiles;
			}
		}
		return null;
	}
}